/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙，邂逅框架梦，本文采用木兰宽松许可证第2版]
 * 
 * https://zhiqim.org/project/zhiqim_kit/zhiqim_git.htm
 *
 * ZhiqimGit is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.zhiqim.git;

import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Set;

import org.zhiqim.git.objer.ObjectLoader;
import org.zhiqim.git.objer.PackFile;
import org.zhiqim.git.objer.PackFilter;
import org.zhiqim.git.objer.PackIndex;
import org.zhiqim.git.objer.loader.UnpackedObjectLoader;
import org.zhiqim.git.objer.objectid.ObjectId;
import org.zhiqim.git.util.GitObjects;

import org.zhiqim.kernel.extend.HashMapSV;
import org.zhiqim.kernel.logging.Log;
import org.zhiqim.kernel.logging.LogFactory;

/**
 * Git对象管理器，松散式包管理
 *
 * 1、${gitMetaDir}/objects/info
 * 2、${gitMetaDir}/objects/pack
 * 3、${gitMetaDir}/objects/${oid[0,2]}/${oid[2,40]}
 * 
 * @version v1.0.0 @author zouzhigang 2017-4-13 新建与整理
 */
public class GitObjer implements GitConstants
{
    private static final Log log = LogFactory.getLog(GitObjer.class);
    private static final PackFilter FILTER = new PackFilter();

    private final File objects;
    private final File infoDirectory;
    private final File packDirectory;
    private final HashMapSV<PackIndex> idxMap;
    
    /******************************************************************************************/
    //构造函数&创建&关闭
    /******************************************************************************************/
    
    public GitObjer(Git git)
    {
        this.objects = new File(git.getMetaDir(), "objects");
        this.infoDirectory = new File(objects, "info");
        this.packDirectory = new File(objects, "pack");
        this.idxMap = new HashMapSV<>();
    }

    /** 创建对象管理器 */
    public void create() throws IOException
    {
        objects.mkdirs();
        infoDirectory.mkdir();
        packDirectory.mkdir();
        
        initIndexMap();
    }
    
    /** 关闭 */
    public void close()
    {
        idxMap.clear();
    }
    
    /** 
     * 初始化索引表，两个地方会调用
     * 1、构造Git，并且存在无错时
     * 2、创建新Git，同时创建Objer时
     * 
     * @throws IOException  该文件异常
     */
    public void initIndexMap() throws IOException
    {
        Set<String> nameList = listPackDirectory();
        for (String indexName : nameList)
        {
            if (indexName.length() != 49 || !indexName.endsWith(_IDX))
            {//非索引文件（包文件）不处理
                continue;
            }
            
            //通过索引名找到包名
            String packName = GitObjects.getPackFileName(indexName);
            if (!nameList.contains(packName))
            {//如果表有idx文件但没有pack文件，跳过，可能是处理失败遗留
                continue;
            }

            //初始化创建包索引，并加到缓存表中
            idxMap.put(indexName, GitObjects.build(new File(packDirectory, indexName)));
        }
    }
    
    /** 
     * 更新索引表，当前在接受到客户端新包重命名成功之后，更新缓存
     */
    public void updateIndexMap(String indexName)
    {
        try
        {
            File idxFile = new File(packDirectory, indexName);
            if (!idxFile.exists())
                idxMap.remove(indexName);
            else
                idxMap.put(indexName, GitObjects.build(idxFile));
        }
        catch(Exception e)
        {
            log.error("更新索引文件["+indexName+"]异常", e);
        }
    }
    
    /******************************************************************************************/
    //目录存在&获取&指定文件
    /******************************************************************************************/
    
    /** 判断是否存在 */
    public boolean exists()
    {
        return objects.exists();
    }
    
    /** 获取对象根目录 */
    public final File getDirectory()
    {
        return objects;
    }
    
    /** 获取对象包目标 */
    public final File getPackDirectory()
    {
        return packDirectory;
    }
    
    /** 获取松散对象文件路径，前2位为目录，后38位为文件 */
    public File fileForUnpacked(ObjectId objectId)
    {
        return fileForUnpacked(objectId.getName());
    }

    /** 获取松散对象文件路径，前2位为目录，后38位为文件 */
    private File fileForUnpacked(String oid)
    {
        String fileDir = oid.substring(0, 2);
        String fileName = oid.substring(2);
        return new File(new File(objects, fileDir), fileName);
    }
    
    /******************************************************************************************/
    //判断对象是否存在（松散&打包）
    /******************************************************************************************/
    
    /** 判断是否存在对象 */
    public final boolean hasObject(ObjectId objectId)
    {
        return hasPackedObject(objectId) || hasUnpackedObject(objectId);
    }
    
    /** 判断是否存在打包文件，查索引文件即可 */
    private final boolean hasPackedObject(ObjectId objectId)
    {
        for (PackIndex idx : idxMap.values())
        {
            if (idx.hasObject(objectId))
                return true;
        }
        
        return false;
    }
    
    /** 判断是否存在松散文件 */
    public final boolean hasUnpackedObject(ObjectId objectId)
    {
        return fileForUnpacked(objectId.getName()).exists();
    }
    
    /******************************************************************************************/
    //获取对象加载器（松散&打包）
    /******************************************************************************************/
    
    /**
     * 获取对象加载器
     * 
     * @param curs          线程临时工作空间
     * @param objectId      对象编号
     * @return              对象加载器
     * @throws IOException  IO异常
     */
    public final ObjectLoader getObjectLoader(ObjectId objectId) throws IOException
    {
        ObjectLoader loader = getPackedObjectLoader(objectId);
        if (loader != null)
            return loader;

        return getUnpackedObjectLoader(objectId);
    }

    /** 获取包对象加载器 */
    private ObjectLoader getPackedObjectLoader(ObjectId objectId) throws IOException
    {
        for (Iterator<Entry<String, PackIndex>> it=idxMap.entrySet().iterator();it.hasNext();)
        {
            Entry<String, PackIndex> entry = it.next();
            PackIndex idx = entry.getValue();
            long offset = idx.findOffset(objectId);
            if (offset == -1)
                continue;
            
            String packName = GitObjects.getPackFileName(entry.getKey());
            File packFile = new File(packDirectory, packName);
            if (!packFile.exists())
            {//删除索引和包文件时可能的文件锁导致删除不完整，存在索引文件，但不存在包文件的情况，删除索引文件
                new File(packDirectory, entry.getKey()).delete();
                it.remove();
                continue;
            }
            
            PackFile pack = new PackFile(idx, packFile);
            return pack.get(offset);
        }
        
        return null;
    }

    /** 获取松散对象加载器 */
    private UnpackedObjectLoader getUnpackedObjectLoader(ObjectId objectId) throws IOException
    {
        File file = fileForUnpacked(objectId.getName());
        if (!file.exists())
            return null;
        
        return new UnpackedObjectLoader(file);
    }
    
    /******************************************************************************************/
    //扫描${gitMetaDir}/objects/pack目录
    /******************************************************************************************/
    
    /** 获取包文件列表 */
    private Set<String> listPackDirectory()
    {
        String[] nameArr = packDirectory.list(FILTER);
        if (nameArr == null)
            return Collections.emptySet();
        
        Set<String> nameSet = new HashSet<>(nameArr.length);
        for (String name : nameArr)
        {
            nameSet.add(name);
        }
        return nameSet;
    }
    
    /******************************************************************************************/
    // toString
    /******************************************************************************************/
    
    @Override
    public String toString()
    {
        return "GitObjer[" + getDirectory() + "]";
    }
}
